/* LN bootstrap -*- mode: java; c-basic-offset: 2; -*- */
/*
LambdaNative - a cross-platform Scheme framework
Copyright (c) 2009-2020, University of British Columbia
All rights reserved.

Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the
following conditions are met:

* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.

* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.

* Neither the name of the University of British Columbia nor
the names of its contributors may be used to endorse or
promote products derived from this software without specific
prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package @SYS_PACKAGE_DOT@;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.content.res.Configuration;
import android.content.pm.PackageManager;
import android.content.pm.PackageInfo;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.KeyEvent;
import android.view.Window;
import android.view.Surface;
import android.view.WindowManager;
import android.content.res.AssetFileDescriptor;
import android.media.AudioManager;
import android.net.Uri;
import android.util.Log;
import java.util.Timer;
import java.util.TimerTask;
import android.location.LocationListener;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
// Imports needed for modules, e.g. gps
@ANDROID_JAVA_IMPORTS@

@ANDROID_JAVA_ADDITIONS@

public class @SYS_APPNAME@ extends Activity implements @ANDROID_JAVA_IMPLEMENTS@ SensorEventListener{
  private static android.view.View current_ContentView = null;
  private SensorManager mSensorManager;
  //Variable declarations needed for modules, e.g. gps
  @ANDROID_JAVA_VARIABLES@

  int idle_tmScheduleRate = 250;
  Timer idle_tm = new Timer();
  TimerTask idle_task = new TimerTask() {
    public void run() {
      runOnUiThread(new Runnable() {
        public void run() {
          nativeEvent(19,0,0); // EVENT_IDLE
        }
      });
    }
  };

  private boolean ln_checkSelfPermission(String permission) {
    @IF_ANDROIDAPI_GT_22@
    if(getApplicationContext().checkSelfPermission(permission)
       !=  android.content.pm.PackageManager.PERMISSION_GRANTED)
      return false;
    /* end of IF_ANDROIDAPI_GT_22 */
    return true;
  }

  public boolean checkOrRequestPermission(String permission) {
    @IF_ANDROIDAPI_GT_22@
    if(!ln_checkSelfPermission(permission)) {
      requestPermissions(new String[] { permission }, 1);
      return ln_checkSelfPermission(permission);
    }
    /* end of IF_ANDROIDAPI_GT_22 */
    return true;
  }

  public boolean requestAllPermissions() {
    @IF_ANDROIDAPI_GT_22@
    PackageManager pm = getPackageManager();
    try
    {
        PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), PackageManager.GET_PERMISSIONS);
        String[] requestedPermissions = null;
        if (packageInfo != null)
            requestedPermissions = packageInfo.requestedPermissions;

        if (requestedPermissions != null && requestedPermissions.length > 0)
          requestPermissions(requestedPermissions, 1);
    }
    catch (PackageManager.NameNotFoundException e)
    {
        System.err.print("requestAllPermissions error: " + e);
        e.printStackTrace(System.err);
        return false;
    }
    /* end of IF_ANDROIDAPI_GT_22 */
    return true;
  }

  @Override
  public void startActivityForResult(Intent intent, int cont) {
      try {
          // System.err.println( "LN: startActivityForResult "  + " for intent " + intent);
          /* TBD we might want to avoid some exceptions for
           * resolveActivity see:
           * https://developer.android.com/training/camera/photobasics
           *
           * if (intent.resolveActivity(getPackageManager()) != null) {...
           */
          super.startActivityForResult(intent, cont);
          // System.err.println( "LN done: startActivityForResult "  + " for intent " + intent);
          // This seems to be a good idea.  But apparently it is not:
          // finishActivity(cont);
      } catch (Exception e) {
          System.err.print("startActivityForResult(Intent " + intent + ") Error: " + e);
          e.printStackTrace(System.err);
      }
  }

  @Override
  public void setContentView(android.view.View view) {
    if(current_ContentView instanceof android.opengl.GLSurfaceView) {
      ((android.opengl.GLSurfaceView)current_ContentView).onPause();
    }
    android.view.ViewParent parent0 = view.getParent();
    if(parent0 instanceof android.view.ViewGroup) {
      android.view.ViewGroup parent = (android.view.ViewGroup) parent0;
      if(parent!=null) {
        parent.removeView(current_ContentView);
      }
    }
    current_ContentView = view;
    super.setContentView(current_ContentView);
    if(current_ContentView instanceof android.opengl.GLSurfaceView) {
      ((android.opengl.GLSurfaceView)current_ContentView).onResume();
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Thread.setDefaultUncaughtExceptionHandler(
      new Thread.UncaughtExceptionHandler() {
        public void uncaughtException(Thread t, Throwable e) {
          final String TAG = "@SYS_PACKAGE_DOT@";
          Log.e(TAG, e.toString());
          try { Thread.sleep(1000); } catch (Exception ex) { }
          System.exit(1);
        }
      });
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    this.requestWindowFeature(Window.FEATURE_NO_TITLE);
    // make sure volume controls control media
    this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
    WindowManager.LayoutParams.FLAG_FULLSCREEN);
    if(mGLView==null) { // once only!
      mGLView = new xGLSurfaceView(this);
      // This may better before other pieces
      nativeInstanceInit(getApplicationContext().getPackageCodePath().toString(), getFilesDir().toString(), getExternalFilesDir(null).toString());
    }

    mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);

    requestAllPermissions();

    // MUST NOT run before nativeInstanceInit completed
    // and MUST NOT run before permission checks
    setContentView(current_ContentView==null ? mGLView : current_ContentView);

    // Additions needed by modules, e.g. gps
    @ANDROID_JAVA_ONCREATE@

    // start EVENT_IDLE
    if(idle_tmScheduleRate > 0) idle_tm.scheduleAtFixedRate(idle_task, 0, idle_tmScheduleRate);
  }

  @Override
  protected void onDestroy() {
    setContentView(mGLView);
    @ANDROID_JAVA_ONDESTROY@
    nativeEvent(14,0,0); // EVENT_CLOSE
    nativeEvent(127,0,0); // EVENT_TERMINATE
    super.onDestroy();
  }
  private void onPauseOrStop() {
    // Additions needed by modules, e.g. gps
    @ANDROID_JAVA_ONPAUSE@
    if (!isFinishing() && current_ContentView==mGLView && mGLView!=null) {
      mGLView.onPause();
    }
  }
  @Override
  protected void onStop() {
    Log.e("@SYS_PACKAGE_DOT@", "onStop");
    onPauseOrStop();
    super.onStop();
  }
  @Override
  protected void onPause() {
    onPauseOrStop();
    super.onPause();
  }
  @Override
  protected void onResume() {
    super.onResume();
    if(current_ContentView==mGLView && mGLView!=null) {
      mGLView.onResume();
    }
    // Additions needed by modules, e.g. gps
    @ANDROID_JAVA_ONRESUME@
  }
  @Override
  public void onAccuracyChanged(Sensor sensor, int accuracy) {
    // can be safely ignored here?
  }
  @Override
  public void onSensorChanged(SensorEvent event) {
    // Extra function calls needed by modules, e.g. gyro
    @ANDROID_JAVA_ONSENSORCHANGED@
  }
  @Override
  public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Functions called when returning to app needed by modules, e.g. camera
    @ANDROID_JAVA_ONACTIVITYRESULT@
  }
  // Extra functions calls needed by modules, e.g. gps
  @ANDROID_JAVA_ACTIVITYADDITIONS@

  // Native event bindings
  static GLSurfaceView mGLView = null;
  native void nativeEvent(int t, int x, int y);
  static { System.loadLibrary("payloadshared"); }
  // OpenURL code
  void openURL(String url) {
    if (!url.startsWith("http://") && !url.startsWith("https://")){
      url = "http://" + url;
    }
    Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
    browserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    if (browserIntent != null) {
     startActivity(browserIntent);
    }
  }

  native void nativeInstanceInit(String packageCodePath, String filesDir, String externalFilesDir);
}

class  xGLSurfaceView extends GLSurfaceView {
  public xGLSurfaceView(Context context) {
    super(context);
    setFocusable(true);
    setFocusableInTouchMode(true);
    renderer = new myRenderer();
    setRenderer(renderer);
  }
  public boolean onTouchEvent(final MotionEvent event) {
    super.onTouchEvent(event);
    t=0;
    x=(int)event.getX(); y=(int)event.getY();
    switch (event.getAction()&MotionEvent.ACTION_MASK) {
      case MotionEvent.ACTION_MOVE:  		t=1; break;
      case MotionEvent.ACTION_DOWN:  		t=5; break;
      case MotionEvent.ACTION_POINTER_DOWN:  	t=5; break;
      case MotionEvent.ACTION_UP:  		t=4; break;
      case MotionEvent.ACTION_POINTER_UP:    	t=4; break;
    }
    if (t>0) {
      final int n=event.getPointerCount();
      final int t0=t;
      final int id0=event.getPointerId(0);
      final int x0=(int)event.getX(0);
      final int y0=(int)event.getY(0);
      if (n>1) { // MultiTouch
        queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(18,id0,0); }});
      }
      queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(t0,x0,y0); }});
      if (n>1) {  // MultiTouch
        final int id1=event.getPointerId(1);
        final int x1=(int)event.getX(1);
        final int y1=(int)event.getY(1);
        queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(18,id1,0); }});
        queueEvent(new Runnable(){ public void run() { renderer.pointerEvent(t0,x1,y1); }});
      }
    }
    return true;
  }
  public boolean onKeyDown(int keyCode, KeyEvent event) {
    return onKeyEvent(keyCode, event, 2); // type == EVENT_KEYPRESS == 2
  }
  public boolean onKeyUp(int keyCode, KeyEvent event) {
    return onKeyEvent(keyCode, event, 3); // type == EVENT_KEYRELEASE == 3
  }
  private boolean onKeyEvent(int keyCode, KeyEvent event, int type) {
    t=x=y=0;
    // Get known keys
    int asciiKey = event.getUnicodeChar(event.getMetaState());
    if (asciiKey > 0) {
      t=type;
      x=asciiKey;
      queueEvent(new Runnable(){ public void run() {
        renderer.nativeEvent(t,x,y); }});
      return true;
    }
    // Get everything else
    switch (keyCode) {
      case KeyEvent.KEYCODE_DEL:         t=type; x=3;  break;
      case KeyEvent.KEYCODE_ENTER:       t=type; x=1;  break;
      case KeyEvent.KEYCODE_DPAD_CENTER: t=type; x=1;  break;
      case KeyEvent.KEYCODE_DPAD_UP:     t=type; x=6;  break;
      case KeyEvent.KEYCODE_DPAD_DOWN:   t=type; x=7;  break;
      case KeyEvent.KEYCODE_DPAD_LEFT:   t=type; x=5;  break;
      case KeyEvent.KEYCODE_DPAD_RIGHT:  t=type; x=4;  break;
      case KeyEvent.KEYCODE_BACK:        t=type; x=10; break;
      case KeyEvent.KEYCODE_MENU:        t=type; x=9;  break;
      case KeyEvent.KEYCODE_VOLUME_DOWN: return false;
      case KeyEvent.KEYCODE_VOLUME_UP:   return false;
      case KeyEvent.KEYCODE_SOFT_LEFT:   return true;
    }
    if (t>0) {
      queueEvent(new Runnable(){ public void run() {
        renderer.nativeEvent(t,x,y); }});
    }
    return true;
  }
  public void onPause() {
    super.onPause();
    renderer.nativeEvent(16,0,0); // EVENT_SUSPEND
  }
  public void onResume() {
    super.onResume();
    renderer.nativeEvent(17,0,0);  // EVENT_RESUME
  }
  int t,x,y;
  myRenderer renderer;
}
class myRenderer implements GLSurfaceView.Renderer {
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    }
    public void onSurfaceChanged(GL10 gl, int w, int h) {
      gl.glViewport(0, 0, w, h);
      width=(float)w; height=(float)h;
      nativeEvent(127,w,h); // EVENT_INIT
    }
   public void onDrawFrame(GL10 gl) {
     nativeEvent(15,0,0);   // EVENT_REDRAW
   }
   public void pointerEvent(int t, int x, int y) { nativeEvent(t,x,(int)height-y); }
   public float width,height;
   native void nativeEvent(int t,int x,int y);
}
